using System;
using System.IO;
using System.Text;
using System.Reflection;
using System.Collections.Generic;

using OpenSim.Region.Framework.Scenes;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Region.Physics.Manager;

using Nini;
using Nini.Config;
using log4net;
using OpenMetaverse;

namespace Playtime.RegionModule
{
	public class RegionModule : ISharedRegionModule
	{
		/// <summary>
		/// Module name reported in logs, etc.
		/// </summary>
		public const string ModuleName = "Playtime";

		/// <summary>
		/// Reference the logger instance
		/// </summary>
		private static readonly ILog mLog = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

		/// <summary>
		/// Reference the nini configuration instance
		/// </summary>
		public IConfig Config { get; private set; }

		/// <summary>
		/// Used in several methods, in order validate if the method should execute
		/// </summary>
		private bool mEnabled = false;

		/// <summary>
		/// Should the script environment allow for code to invoke Lua garbage collection?
		/// </summary>
		private bool mLuaAllowCollectGarbage = false;
		
		/// <summary>
		/// Track all regions the module is connected to
		/// </summary>
		private Dictionary<UUID, Scene> mScenes = new Dictionary<UUID, Scene>();

		/// <summary>
		///  The Avatars we are tracking (only roots)
		/// </summary>
		private Dictionary<UUID, AvatarConfiguration> mAvatars = new Dictionary<UUID, AvatarConfiguration>();
		
		/// <summary>
		/// All known profiles associated with this sim
		/// </summary>
		private Dictionary<UUID, Profile> mProfiles = new Dictionary<UUID, Profile>();
		
		/// <summary>
		/// All known attributes, across all profiles (enables sharing of attributes)
		/// </summary>
		private Dictionary<UUID, Attribute> mAttributes = new Dictionary<UUID, Attribute>();
		
		#region ISharedRegionModule implementation
		public void PostInitialise()
		{
			mLog.Debug("[" + ModuleName + "]" + " PostInitialise");
		}
		
		AttributeInstance.LuaApi a1 = AttributeInstance.LuaApi.Singleton;
		IntegerAttributeInstance.LuaApi a2 = IntegerAttributeInstance.LuaApi.Singleton;
		FloatAttributeInstance.LuaApi a3 = FloatAttributeInstance.LuaApi.Singleton;
		BoolAttributeInstance.LuaApi a4 = BoolAttributeInstance.LuaApi.Singleton;
		StringAttributeInstance.LuaApi a5 = StringAttributeInstance.LuaApi.Singleton;
		
		public string Name { get { return ModuleName; } }
		public Type ReplaceableInterface { get { return null; } }

		public void Initialise(IConfigSource source)
		{
			mLog.Debug("[" + ModuleName + "]" + " Initializing");
            Config = source.Configs[ModuleName];

            if(Config == null)
			{
	            mLog.Error("[" + ModuleName + "]" + " The module is not configured, exiting");
				return;
			}

            mEnabled = Config.GetBoolean("Enabled", false);
            if(!mEnabled)
			{
	            mLog.Info("[" + ModuleName + "]" + " Disabled");
				return;
			}
			mLog.Info("[" + ModuleName + "]" + " Enabled");

			mLuaAllowCollectGarbage = Config.GetBoolean("AllowCollectGarbage", false);

			// load the profiles describing which attributes to handle
			foreach(string key in Config.GetKeys())
				if(key.ToLower().StartsWith("profile."))
				{
					string profileName = Config.Get(key);
					string pathName = Path.Combine("playtime", profileName) + ".ini";
					try
					{
						Profile p = new Profile(new IniConfigSource(pathName), mLuaAllowCollectGarbage);
						mProfiles.Add(p.ID, p);
					
						// attributes may be shared between profiles
					    // fixup references to attributes
						foreach(Attribute a in p.Attributes.Values)
						{
							if(mAttributes.ContainsKey(a.ID))
								p.Attributes[a.ID] = mAttributes[a.ID];
							else
								mAttributes.Add(a.ID, a);
							a.Profiles.Add(p);
						}
					
					}
					catch(DirectoryNotFoundException e)
					{
						mLog.WarnFormat("[" + ModuleName + "]" + " {0}", e.Message);
					}
				}
			
			// log loaded profiles
			mLog.Debug("[" + ModuleName + "]" + " Loaded profiles:" + mProfiles.Count);
			foreach(Profile p in mProfiles.Values)
				mLog.DebugFormat("[" + ModuleName + "]" + " -- {0}", p.Name);
			
			// log loaded attributes
			mLog.Debug("[" + ModuleName + "]" + " Loaded attributes:" + mAttributes.Count);
			foreach(Attribute a in mAttributes.Values)
				mLog.DebugFormat("[" + ModuleName + "]" + " -- {0}", a.Name);
		}
		
		public void AddRegion(Scene scene)
		{
			// if we aren't enabled, just leave this silently
			if(!mEnabled)
			{
				mLog.Info("[" + ModuleName + "]" + " Engine is not enabled, refusing to add " + scene.RegionInfo.RegionName + " region");
				return;
			}

			mScenes.Add(scene.RegionInfo.RegionID, scene);
			
			scene.EventManager.OnNewPresence += delegate(ScenePresence presence) {
				mLog.Info("[" + ModuleName + "]" + " NewPresence");
				if(!mAvatars.ContainsKey(presence.UUID))
				{
					SetupAvatarConfiguration(presence);
				} 
			};

			scene.EventManager.OnMakeRootAgent += delegate(ScenePresence presence) {
				mLog.Info("[" + ModuleName + "]" + " MakeRoot");
				if(mAvatars.ContainsKey(presence.UUID))
				{
					mAvatars[presence.UUID].StartListenForCollisions(presence);
				}
				else
					mLog.Warn("[" + ModuleName + "]" + " MakeRoot on unknown avatar");
			};
			
			scene.EventManager.OnMakeChildAgent += delegate(ScenePresence presence) {
				mLog.Info("[" + ModuleName + "]" + " MakeChild");
				if(mAvatars.ContainsKey(presence.UUID))
				{
					mAvatars[presence.UUID].StopListenForCollisions();
				}
				else
					mLog.Warn("[" + ModuleName + "]" + " MakeRoot on unknown avatar");
			};
			
			scene.EventManager.OnRemovePresence += delegate(UUID agentId) {
				mLog.Info("[" + ModuleName + "]" + " RemovePresence");

				if(mAvatars.ContainsKey(agentId))
					mAvatars.Remove(agentId);
			};
		}

		
		public void RemoveRegion(Scene scene)
		{
			mLog.Debug("[" + ModuleName + "]" + " RemoveRegion");
			if(mEnabled)
			{
//				RegionHandler region = mScenes[scene.RegionInfo.RegionID];
//				region.Dispose();
//				mScenes.Remove(scene.RegionInfo.RegionID);
			}
		}

		private void SetupAvatarConfiguration(ScenePresence presence)
		{
			AvatarConfiguration cfg = new AvatarConfiguration(presence, mProfiles.Values);
			mAvatars.Add(presence.UUID, cfg);
			cfg.ExecuteInitializeHook();
		}
		
		/// <summary>
		/// Called when the simulator has finished loading the region
		/// </summary>
		/// <param name="scene">
		/// A <see cref="Scene"/>
		/// </param>
		public void RegionLoaded(Scene scene)
		{
		}

		public void Close()
        {
			if(!mEnabled)
				return;
				
			// there is a bug in OpenSim, causing "Close" to be invoked several times,
			// this should guard against closing down prematurely
//			if(mScenes.Count == 0)
//			{
				mLog.Debug("[" + ModuleName + "]" + " Terminating");
//			}
        }
		#endregion
	}
}

